home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / src / mail / pine3.96.tar.gz / pine3.96.tar / pine3.96 / imap / ANSI / c-client / rfc822.c < prev    next >
C/C++ Source or Header  |  1996-04-09  |  58KB  |  1,686 lines

  1. /*
  2.  * Program:    RFC-822 routines (originally from SMTP)
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    27 July 1988
  13.  * Last Edited:    9 April 1996
  14.  *
  15.  * Sponsorship:    The original version of this work was developed in the
  16.  *        Symbolic Systems Resources Group of the Knowledge Systems
  17.  *        Laboratory at Stanford University in 1987-88, and was funded
  18.  *        by the Biomedical Research Technology Program of the National
  19.  *        Institutes of Health under grant number RR-00785.
  20.  *
  21.  * Original version Copyright 1988 by The Leland Stanford Junior University
  22.  * Copyright 1996 by the University of Washington
  23.  *
  24.  *  Permission to use, copy, modify, and distribute this software and its
  25.  * documentation for any purpose and without fee is hereby granted, provided
  26.  * that the above copyright notices appear in all copies and that both the
  27.  * above copyright notices and this permission notice appear in supporting
  28.  * documentation, and that the name of the University of Washington or The
  29.  * Leland Stanford Junior University not be used in advertising or publicity
  30.  * pertaining to distribution of the software without specific, written prior
  31.  * permission.  This software is made available "as is", and
  32.  * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
  33.  * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
  34.  * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  35.  * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
  36.  * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
  37.  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  38.  * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  39.  * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
  40.  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  41.  *
  42.  */
  43.  
  44.  
  45. #include <ctype.h>
  46. #include <stdio.h>
  47. #include <time.h>
  48. #include "mail.h"
  49. #include "osdep.h"
  50. #include "rfc822.h"
  51. #include "misc.h"
  52.  
  53. /* RFC-822 static data */
  54.  
  55.  
  56. char *errhst = ".SYNTAX-ERROR."; /* syntax error host string */
  57.  
  58.  
  59. /* Body formats constant strings, must match definitions in mail.h */
  60.  
  61. char *body_types[TYPEMAX+1] = {
  62.   "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
  63.   "X-UNKNOWN"
  64. };
  65.  
  66.  
  67. char *body_encodings[ENCMAX+1] = {
  68.   "7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN"
  69. };
  70.  
  71.  
  72. /* Token delimiting special characters */
  73.  
  74.                 /* full RFC-822 specials */
  75. const char *rspecials =  "()<>@,;:\\\"[].";
  76.                 /* body token specials */
  77. const char *tspecials = " ()<>@,;:\\\"[]./?=";
  78.  
  79.  
  80. /* Once upon a time, CSnet had a mailer which assigned special semantics to
  81.  * dot in e-mail addresses.  For the sake of that mailer, dot was added to
  82.  * the RFC-822 definition of `specials', even though it had numerous bad side
  83.  * effects:
  84.  *   1)    It broke mailbox names on systems which had dots in user names, such as
  85.  *    Multics and TOPS-20.  RFC-822's syntax rules require that `Admin . MRC'
  86.  *    be considered equivalent to `Admin.MRC'.  Fortunately, few people ever
  87.  *    tried this in practice.
  88.  *   2) It required that all personal names with an initial be quoted, a widely
  89.  *    detested user interface misfeature.
  90.  *   3)    It made the parsing of host names be non-atomic for no good reason.
  91.  * To work around these problems, the following alternate specials lists are
  92.  * defined.  hspecials and wspecials are used in lieu of rspecials, and
  93.  * ptspecials are used in lieu of tspecials.  These alternate specials lists
  94.  * make the parser work a lot better in the real world.  It ain't politically
  95.  * correct, but it lets the users get their job done!
  96.  */
  97.  
  98.                 /* parse-host specials */
  99. const char *hspecials = " ()<>@,;:\\\"";
  100.                 /* parse-word specials */
  101. const char *wspecials = " ()<>@,;:\\\"[]";
  102.                 /* parse-token specials for parsing */
  103. const char *ptspecials = " ()<>@,;:\\\"[]/?=";
  104.  
  105. /* RFC822 writing routines */
  106.  
  107.  
  108. /* Write RFC822 header from message structure
  109.  * Accepts: scratch buffer to write into
  110.  *        message envelope
  111.  *        message body
  112.  */
  113.  
  114. void rfc822_header (char *header,ENVELOPE *env,BODY *body)
  115. {
  116.   if (env->remail) {        /* if remailing */
  117.     long i = strlen (env->remail);
  118.                 /* flush extra blank line */
  119.     if (i > 4 && env->remail[i-4] == '\015') env->remail[i-2] = '\0';
  120.     strcpy (header,env->remail);/* start with remail header */
  121.   }
  122.   else *header = '\0';        /* else initialize header to null */
  123.   rfc822_header_line (&header,"Newsgroups",env,env->newsgroups);
  124.   rfc822_header_line (&header,"Date",env,env->date);
  125.   rfc822_address_line (&header,"From",env,env->from);
  126.   rfc822_address_line (&header,"Sender",env,env->sender);
  127.   rfc822_address_line (&header,"Reply-To",env,env->reply_to);
  128.   rfc822_header_line (&header,"Subject",env,env->subject);
  129.   if (env->bcc && !(env->to || env->cc))
  130.     strcat (header,"To: undisclosed recipients: ;\015\012");
  131.   rfc822_address_line (&header,"To",env,env->to);
  132.   rfc822_address_line (&header,"cc",env,env->cc);
  133. /* bcc's are never written...
  134.  * rfc822_address_line (&header,"bcc",env,env->bcc);
  135.  */
  136.   rfc822_header_line (&header,"In-Reply-To",env,env->in_reply_to);
  137.   rfc822_header_line (&header,"Message-ID",env,env->message_id);
  138.   rfc822_header_line (&header,"Followup-to",env,env->followup_to);
  139.   rfc822_header_line (&header,"References",env,env->references);
  140.   if (body && !env->remail) {    /* not if remail or no body structure */
  141.     strcat (header,"MIME-Version: 1.0\015\012");
  142.     rfc822_write_body_header (&header,body);
  143.   }
  144.   strcat (header,"\015\012");    /* write terminating blank line */
  145. }
  146.  
  147. /* Write RFC822 address from header line
  148.  * Accepts: pointer to destination string pointer
  149.  *        pointer to header type
  150.  *        message to interpret
  151.  *        address to interpret
  152.  */
  153.  
  154. void rfc822_address_line (char **header,char *type,ENVELOPE *env,ADDRESS *adr)
  155. {
  156.   char *t,tmp[MAILTMPLEN];
  157.   long i,len,n = 0;
  158.   char *s = (*header += strlen (*header));
  159.   if (adr) {            /* do nothing if no addresses */
  160.     if (env && env->remail) strcat (s,"ReSent-");
  161.     strcat (s,type);        /* write header name */
  162.     strcat (s,": ");
  163.     s += (len = strlen (s));    /* initial string length */
  164.     do {            /* run down address list */
  165.       *(t = tmp) = '\0';    /* initially empty string */
  166.                 /* start of group? */
  167.       if (adr->mailbox && !adr->host) {
  168.                 /* yes, write group name */
  169.     rfc822_cat (t,adr->mailbox,rspecials);
  170.     strcat (t,": ");    /* write group identifier */
  171.     n++;            /* in a group, suppress expansion */
  172.       }
  173.       else {            /* not start of group */
  174.     if (!adr->host && n) {    /* end of group? */
  175.       strcat (t,";");    /* write close delimiter */
  176.       n--;            /* no longer in a group */
  177.     }
  178.     else if (!n) {        /* only print if not inside a group */
  179.                 /* simple case? */
  180.       if (!(adr->personal || adr->adl)) rfc822_address (t,adr);
  181.       else {        /* no, must use phrase <route-addr> form */
  182.         if (adr->personal) rfc822_cat (t,adr->personal,rspecials);
  183.         strcat (t," <");    /* write address delimiter */
  184.                 /* write address */
  185.         rfc822_address (t,adr);
  186.         strcat (t,">");    /* closing delimiter */
  187.       }
  188.     }
  189.                 /* write delimiter for next recipient */
  190.     if (!n && adr->next && adr->next->mailbox) strcat (t,", ");
  191.       }
  192.                 /* if string would overflow */
  193.       if ((len += (i = strlen (t))) > 78) {
  194.     len = 4 + i;        /* continue it on a new line */
  195.     *s++ = '\015'; *s++ = '\012';
  196.     *s++ = ' '; *s++ = ' '; *s++ = ' '; *s++ = ' ';
  197.       }
  198.       while (*t) *s++ = *t++;    /* write this address */
  199.     } while (adr = adr->next);
  200.                 /* tie off header line */
  201.     *s++ = '\015'; *s++ = '\012'; *s = '\0';
  202.     *header = s;        /* set return value */
  203.   }
  204. }
  205.  
  206. /* Write RFC822 text from header line
  207.  * Accepts: pointer to destination string pointer
  208.  *        pointer to header type
  209.  *        message to interpret
  210.  *        pointer to text
  211.  */
  212.  
  213. void rfc822_header_line (char **header,char *type,ENVELOPE *env,char *text)
  214. {
  215.   if (text) sprintf ((*header += strlen (*header)),"%s%s: %s\015\012",
  216.              env->remail ? "ReSent-" : "",type,text);
  217. }
  218.  
  219.  
  220. /* Write RFC822 address
  221.  * Accepts: pointer to destination string
  222.  *        address to interpret
  223.  */
  224.  
  225. void rfc822_write_address (char *dest,ADDRESS *adr)
  226. {
  227.   while (adr) {
  228.                 /* start of group? */
  229.     if (adr->mailbox && !adr->host) {
  230.                 /* yes, write group name */
  231.       rfc822_cat (dest,adr->mailbox,rspecials);
  232.       strcat (dest,": ");    /* write group identifier */
  233.       adr = adr->next;        /* move to next address block */
  234.     }
  235.     else {            /* end of group? */
  236.       if (!adr->host) strcat (dest,";");
  237.                 /* simple case? */
  238.       else if (!(adr->personal || adr->adl)) rfc822_address (dest,adr);
  239.       else {            /* no, must use phrase <route-addr> form */
  240.     if (adr->personal) {    /* in case have adl but no personal name */
  241.       rfc822_cat (dest,adr->personal,rspecials);
  242.       strcat (dest," ");
  243.     }
  244.     strcat (dest,"<");    /* write address delimiter */
  245.     rfc822_address (dest,adr);/* write address */
  246.     strcat (dest,">");    /* closing delimiter */
  247.       }
  248.                 /* delimit if there is one */
  249.       if ((adr = adr->next) && adr->mailbox) strcat (dest,", ");
  250.     }
  251.   }
  252. }
  253.  
  254. /* Write RFC822 route-address to string
  255.  * Accepts: pointer to destination string
  256.  *        address to interpret
  257.  */
  258.  
  259. void rfc822_address (char *dest,ADDRESS *adr)
  260. {
  261.   if (adr && adr->host) {    /* no-op if no address */
  262.     if (adr->adl) {        /* have an A-D-L? */
  263.       strcat (dest,adr->adl);
  264.       strcat (dest,":");
  265.     }
  266.                 /* write mailbox name */
  267.     rfc822_cat (dest,adr->mailbox,wspecials);
  268.     if (*adr->host != '@') {    /* unless null host (HIGHLY discouraged!) */
  269.       strcat (dest,"@");    /* host delimiter */
  270.       strcat (dest,adr->host);    /* write host name */
  271.     }
  272.   }
  273. }
  274.  
  275.  
  276. /* Concatenate RFC822 string
  277.  * Accepts: pointer to destination string
  278.  *        pointer to string to concatenate
  279.  *        list of special characters
  280.  */
  281.  
  282. void rfc822_cat (char *dest,char *src,const char *specials)
  283. {
  284.   char *s;
  285.   if (strpbrk (src,specials)) {    /* any specials present? */
  286.     strcat (dest,"\"");        /* opening quote */
  287.                 /* truly bizarre characters in there? */
  288.     while (s = strpbrk (src,"\\\"")) {
  289.       strncat (dest,src,s-src);    /* yes, output leader */
  290.       strcat (dest,"\\");    /* quoting */
  291.       strncat (dest,s,1);    /* output the bizarre character */
  292.       src = ++s;        /* continue after the bizarre character */
  293.     }
  294.     if (*src) strcat (dest,src);/* output non-bizarre string */
  295.     strcat (dest,"\"");        /* closing quote */
  296.   }
  297.   else strcat (dest,src);    /* otherwise it's the easy case */
  298. }
  299.  
  300. /* Write body content header
  301.  * Accepts: pointer to destination string pointer
  302.  *        pointer to body to interpret
  303.  */
  304.  
  305. void rfc822_write_body_header (char **dst,BODY *body)
  306. {
  307.   char *s;
  308.   PARAMETER *param = body->parameter;
  309.   sprintf (*dst += strlen (*dst),"Content-Type: %s",body_types[body->type]);
  310.   s = body->subtype ? body->subtype : rfc822_default_subtype (body->type);
  311.   sprintf (*dst += strlen (*dst),"/%s",s);
  312.   if (param) do {
  313.     sprintf (*dst += strlen (*dst),"; %s=",param->attribute);
  314.     rfc822_cat (*dst,param->value,tspecials);
  315.   } while (param = param->next);
  316.   else if (body->type == TYPETEXT) strcat (*dst,"; CHARSET=US-ASCII");
  317.   strcpy (*dst += strlen (*dst),"\015\012");
  318.   if (body->encoding)        /* note: encoding 7BIT never output! */
  319.     sprintf (*dst += strlen (*dst),"Content-Transfer-Encoding: %s\015\012",
  320.          body_encodings[body->encoding]);
  321.   if (body->id) sprintf (*dst += strlen (*dst),"Content-ID: %s\015\012",
  322.              body->id);
  323.   if (body->description)
  324.     sprintf (*dst += strlen (*dst),"Content-Description: %s\015\012",
  325.          body->description);
  326.   if (body->md5)
  327.     sprintf (*dst += strlen (*dst),"Content-MD5: %s\015\012",body->md5);
  328. }
  329.  
  330.  
  331. /* Subtype defaulting (a no-no, but regretably necessary...)
  332.  * Accepts: type code
  333.  * Returns: default subtype name
  334.  */
  335.  
  336. char *rfc822_default_subtype (unsigned short type)
  337. {
  338.   switch (type) {
  339.   case TYPETEXT:        /* default is TEXT/PLAIN */
  340.     return "PLAIN";
  341.   case TYPEMULTIPART:        /* default is MULTIPART/MIXED */
  342.     return "MIXED";
  343.   case TYPEMESSAGE:        /* default is MESSAGE/RFC822 */
  344.     return "RFC822";
  345.   case TYPEAPPLICATION:        /* default is APPLICATION/OCTET-STREAM */
  346.     return "OCTET-STREAM";
  347.   case TYPEAUDIO:        /* default is AUDIO/BASIC */
  348.     return "BASIC";
  349.   default:            /* others have no default subtype */
  350.     return "UNKNOWN";
  351.   }
  352. }
  353.  
  354. /* RFC822 parsing routines */
  355.  
  356.  
  357. /* Parse an RFC822 message
  358.  * Accepts: pointer to return envelope
  359.  *        pointer to return body
  360.  *        pointer to header
  361.  *        header byte count
  362.  *        pointer to body stringstruct
  363.  *        pointer to local host name
  364.  *        pointer to scratch buffer
  365.  */
  366.  
  367. void rfc822_parse_msg (ENVELOPE **en,BODY **bdy,char *s,unsigned long i,
  368.                STRING *bs,char *host,char *tmp)
  369. {
  370.   char c,*t,*d;
  371.   ENVELOPE *env = (*en = mail_newenvelope ());
  372.   BODY *body = bdy ? (*bdy = mail_newbody ()) : NIL;
  373.   long MIMEp = NIL;        /* flag that MIME semantics are in effect */
  374.   long PathP = NIL;        /* flag that a Path: was seen */
  375.   while (i && *s != '\n') {    /* until end of header */
  376.     t = tmp;            /* initialize buffer pointer */
  377.     c = ' ';            /* and previous character */
  378.     while (i && c) {        /* collect text until logical end of line */
  379.       switch (c = *s++) {    /* slurp a character */
  380.       case '\015':        /* return, possible end of logical line */
  381.     if (*s == '\n') break;    /* ignore if LF follows */
  382.       case '\012':        /* LF, possible end of logical line */
  383.                 /* tie off unless next line starts with WS */
  384.     if (*s != ' ' && *s != '\t') *t++ = c = '\0';
  385.     break;
  386.       case '\t':        /* tab */
  387.     *t++ = ' ';        /* coerce to space */
  388.     break;
  389.       default:            /* all other characters */
  390.     *t++ = c;        /* insert the character into the line */
  391.     break;
  392.       }
  393.       if (!--i) *t++ = '\0';    /* see if end of header */
  394.     }
  395.  
  396.                 /* find header item type */
  397.     if (t = d = strchr (tmp,':')) {
  398.       *d++ = '\0';        /* tie off header item, point at its data */
  399.       while (*d == ' ') d++;    /* flush whitespace */
  400.       while ((tmp < t--) && (*t == ' ')) *t = '\0';
  401.       switch (*ucase (tmp)) {    /* dispatch based on first character */
  402.       case '>':            /* possible >From: */
  403.     if (!strcmp (tmp+1,"FROM")) rfc822_parse_adrlist (&env->from,d,host);
  404.     break;
  405.       case 'B':            /* possible bcc: */
  406.     if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host);
  407.     break;
  408.       case 'C':            /* possible cc: or Content-<mumble>*/
  409.     if (!strcmp (tmp+1,"C")) rfc822_parse_adrlist (&env->cc,d,host);
  410.     else if ((tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') &&
  411.          (tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') &&
  412.          (tmp[7] == '-') && body &&
  413.          (MIMEp || (search (s-1,i,"\012MIME-Version",(long) 13))))
  414.       rfc822_parse_content_header (body,tmp+8,d);
  415.     break;
  416.       case 'D':            /* possible Date: */
  417.     if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d);
  418.     break;
  419.       case 'F':            /* possible From: */
  420.     if (!strcmp (tmp+1,"ROM")) rfc822_parse_adrlist (&env->from,d,host);
  421.     else if (!strcmp (tmp+1,"OLLOWUP-TO")) {
  422.       t = env->followup_to = (char *) fs_get (1 + strlen (d));
  423.       while (c = *d++) if (c != ' ') *t++ = c;
  424.       *t++ = '\0';
  425.     }
  426.     break;
  427.       case 'I':            /* possible In-Reply-To: */
  428.     if (!env->in_reply_to && !strcmp (tmp+1,"N-REPLY-TO"))
  429.       env->in_reply_to = cpystr (d);
  430.     break;
  431.       case 'M':            /* possible Message-ID: or MIME-Version: */
  432.     if (!env->message_id && !strcmp (tmp+1,"ESSAGE-ID"))
  433.       env->message_id = cpystr (d);
  434.     else if (!strcmp (tmp+1,"IME-VERSION")) {
  435.                 /* tie off at end of phrase */
  436.       if (t = rfc822_parse_phrase (d)) *t = '\0';
  437.       rfc822_skipws (&d);    /* skip whitespace */
  438.                 /* known version? */
  439.       if (strcmp (d,"1.0") && strcmp (d,"RFC-XXXX"))
  440.         mm_log ("Warning: message has unknown MIME version",PARSE);
  441.       MIMEp = T;        /* note that we are MIME */
  442.     }
  443.     break;
  444.       case 'N':            /* possible Newsgroups: */
  445.     if (!env->newsgroups && !strcmp (tmp+1,"EWSGROUPS")) {
  446.       t = env->newsgroups = (char *) fs_get (1 + strlen (d));
  447.       while (c = *d++) if (c != ' ') *t++ = c;
  448.       *t++ = '\0';
  449.     }
  450.     break;
  451.  
  452.       case 'P':            /* possible Path: */
  453.     if (!strcmp (tmp+1,"ATH")) PathP = T;
  454.     break;
  455.       case 'R':            /* possible Reply-To: */
  456.     if (!strcmp (tmp+1,"EPLY-TO"))
  457.       rfc822_parse_adrlist (&env->reply_to,d,host);
  458.     else if (!env->references && !strcmp (tmp+1,"EFERENCES"))
  459.       env->references = cpystr (d);
  460.     break;
  461.       case 'S':            /* possible Subject: or Sender: */
  462.     if (!env->subject && !strcmp (tmp+1,"UBJECT"))
  463.       env->subject = cpystr (d);
  464.     else if (!strcmp (tmp+1,"ENDER"))
  465.       rfc822_parse_adrlist (&env->sender,d,host);
  466.     break;
  467.       case 'T':            /* possible To: */
  468.     if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host);
  469.     break;
  470.       default:
  471.     break;
  472.       }
  473.     }
  474.   }
  475.   /* We require a Path: header and/or a Message-ID belonging to a known
  476.    * winning mail program, in order to believe Newsgroups:.  This is because
  477.    * of the unfortunate existance of certain cretins who believe that it
  478.    * is reasonable to transmit messages via SMTP with a "Newsgroups" header
  479.    * that were not actually posted to any of the named newsgroups.
  480.    * The authors of other high-quality email/news software are encouraged to
  481.    * use similar methods to indentify messages as coming from their software,
  482.    * and having done so, to tell us so they too can be blessed in this list.
  483.    */
  484.   if (env->newsgroups && !PathP && env->message_id &&
  485.       strncmp (env->message_id,"<Pine.",6) &&
  486.       strncmp (env->message_id,"<MS-C.",6) &&
  487.       strncmp (env->message_id,"<MailManager.",13) &&
  488.       strncmp (env->message_id,"<EasyMail.",11) &&
  489.       strncmp (env->message_id,"<ML-",4)) {
  490.     sprintf (tmp,"Probable bogus newsgroup list \"%s\" in \"%s\" ignored",
  491.          env->newsgroups,env->message_id);
  492.     mm_log (tmp,PARSE);
  493.     fs_give ((void **) &env->newsgroups);
  494.   }
  495.                 /* default Sender: and Reply-To: to From: */
  496.   if (!env->sender) env->sender = rfc822_cpy_adr (env->from);
  497.   if (!env->reply_to) env->reply_to = rfc822_cpy_adr (env->from);
  498.                 /* now parse the body */
  499.   if (body) rfc822_parse_content (body,bs,host,tmp);
  500. }
  501.  
  502. /* Parse a message body content
  503.  * Accepts: pointer to body structure
  504.  *        body string
  505.  *        pointer to local host name
  506.  *        pointer to scratch buffer
  507.  */
  508.  
  509. void rfc822_parse_content (BODY *body,STRING *bs,char *h,char *t)
  510. {
  511.   char c,c1,*s,*s1;
  512.   int f;
  513.   unsigned long pos = GETPOS (bs);
  514.   unsigned long i = SIZE (bs);
  515.   unsigned long j,k,m = 0;
  516.   PARAMETER *param;
  517.   PART *part = NIL;
  518.   body->size.ibytes = i;    /* note body size in all cases */
  519.   body->size.bytes = ((body->encoding == ENCBINARY) ||
  520.               (body->type == TYPEMULTIPART)) ? i : strcrlflen (bs);
  521.   switch (body->type) {        /* see if anything else special to do */
  522.   case TYPETEXT:        /* text content */
  523.     if (!body->subtype)        /* default subtype */
  524.       body->subtype = cpystr (rfc822_default_subtype (body->type));
  525.     if (!body->parameter) {    /* default parameters */
  526.       body->parameter = mail_newbody_parameter ();
  527.       body->parameter->attribute = cpystr ("CHARSET");
  528.       body->parameter->value = cpystr ("US-ASCII");
  529.     }
  530.                 /* count number of lines */
  531.     while (i--) if ((SNX (bs)) == '\n') body->size.lines++;
  532.     break;
  533.  
  534.   case TYPEMESSAGE:        /* encapsulated message */
  535.     body->contents.msg.env = NIL;
  536.     body->contents.msg.body = NIL;
  537.     body->contents.msg.text = NIL;
  538.     body->contents.msg.offset = pos;
  539.                 /* encapsulated RFC-822 message? */
  540.     if (!strcmp (body->subtype,"RFC822")) {
  541.       if ((body->encoding == ENCBASE64) ||
  542.       (body->encoding == ENCQUOTEDPRINTABLE)
  543.       || (body->encoding == ENCOTHER))
  544.     mm_log ("Ignoring nested encoding of message contents",PARSE);
  545.                 /* hunt for blank line */
  546.       for (c = '\012',j = 0; (i > j) && ((c != '\012') || (CHR(bs) != '\012'));
  547.        j++) if ((c1 = SNX (bs)) != '\015') c = c1;
  548.       if (i > j) c1 = SNX (bs);    /* unless no more text, body starts here */
  549.                 /* note body text offset and header size */
  550.       j = (body->contents.msg.offset = GETPOS (bs)) - pos;
  551.       SETPOS (bs,pos);        /* copy header string */
  552.       s = (char *) fs_get ((size_t) j + 1);
  553.       for (s1 = s,k = j; k--; *s1++ = SNX (bs));
  554.       s[j] = '\0';        /* tie off string (not really necessary) */
  555.                 /* now parse the body */
  556.       rfc822_parse_msg (&body->contents.msg.env,&body->contents.msg.body,s,j,
  557.             bs,h,t);
  558.       fs_give ((void **) &s);    /* free header string */
  559.       SETPOS (bs,pos);        /* restore position */
  560.     }
  561.                 /* count number of lines */
  562.     while (i--) if (SNX (bs) == '\n') body->size.lines++;
  563.     break;
  564.  
  565.   case TYPEMULTIPART:        /* multiple parts */
  566.                 /* remember if digest */
  567.     f = !strcmp (body->subtype,"DIGEST");
  568.     if ((body->encoding == ENCBASE64) || (body->encoding == ENCQUOTEDPRINTABLE)
  569.     || (body->encoding == ENCOTHER))
  570.       mm_log ("Ignoring nested encoding of multipart contents",PARSE);
  571.                 /* find cookie */
  572.     for (*t = '\0',param = body->parameter; param && !*t; param = param->next)
  573.       if (!strcmp (param->attribute,"BOUNDARY")) strcpy (t,param->value);
  574.     if (!*t) strcpy (t,"-");    /* yucky default */
  575.     j = strlen (t);        /* length of cookie and header */
  576.     c = '\012';            /* initially at beginning of line */
  577.     while (i > j) {        /* examine data */
  578.       m = GETPOS (bs);        /* note position */
  579.       if (m) m--;        /* get position in front of character */
  580.       switch (c) {        /* examine each line */
  581.       case '\015':        /* handle CRLF form */
  582.     if (CHR (bs) == '\012'){/* following LF? */
  583.       c = SNX (bs); i--;    /* yes, slurp it */
  584.     }
  585.       case '\012':        /* at start of a line, start with -- ? */
  586.     if (i-- && ((c = SNX (bs)) == '-') && i-- && ((c = SNX (bs)) == '-')) {
  587.                 /* see if cookie matches */
  588.       for (k = j,s = t; i-- && *s++ == (c = SNX (bs)) && --k;);
  589.       if (k) break;        /* strings didn't match if non-zero */
  590.                 /* look at what follows cookie */
  591.       if (i && i--) switch (c = SNX (bs)) {
  592.       case '-':        /* at end if two dashes */
  593.         if ((i && i--) && ((c = SNX (bs)) == '-') &&
  594.         ((i && i--) ? (((c = SNX (bs)) == '\015') || (c=='\012')):T)) {
  595.                 /* if have a final part calculate its size */
  596.           if (part) part->body.size.bytes = (m > part->offset) ?
  597.         (m - part->offset) : 0;
  598.           part = NIL; i = 1; /* terminate scan */
  599.         }
  600.         break;
  601.       case '\015':        /* handle CRLF form */
  602.         if (i && CHR (bs) == '\012') {
  603.           c = SNX (bs); i--;/* yes, slurp it */
  604.         }
  605.       case '\012':        /* new line */
  606.         if (part) {        /* calculate size of previous */
  607.           part->body.size.bytes = (m>part->offset) ? (m-part->offset) : 0;
  608.                 /* instantiate next */
  609.           part = part->next = mail_newbody_part ();
  610.         }            /* otherwise start new list */
  611.         else part = body->contents.part = mail_newbody_part ();
  612.                 /* digest has a different default */
  613.         if (f) part->body.subtype =
  614.           cpystr (rfc822_default_subtype (part->body.type = TYPEMESSAGE));
  615.                 /* note offset from main body */
  616.         part->offset = GETPOS (bs);
  617.         break;
  618.       default:        /* whatever it was it wasn't valid */
  619.         break;
  620.       }
  621.     }
  622.     break;
  623.       default:            /* not at a line */
  624.     c = SNX (bs); i--;    /* get next character */
  625.     break;
  626.       }                /* calculate size of any final part */
  627.     }
  628.     if (part) part->body.size.bytes = i + ((GETPOS (bs) > part->offset) ?
  629.                        (GETPOS (bs) - part->offset) : 0);
  630.  
  631.                 /* parse body parts */
  632.     for (part = body->contents.part; part; part = part->next) {
  633.       SETPOS (bs,part->offset);    /* move to that part of the body */
  634.                 /* get size of this part, ignore if empty */
  635.       if (i = part->body.size.bytes) {
  636.                 /* until end of header */
  637.     while (i && ((c = CHR (bs)) != '\015') && (c != '\012')) {
  638.       s1 = t;        /* initialize buffer pointer */
  639.       c = ' ';        /* and previous character */
  640.       while (c) {        /* collect text until logical end of line */
  641.         switch (c1 = SNX (bs)) {
  642.         case '\015':    /* return */
  643.           if (i && (CHR (bs) == '\012')) {
  644.         c1 = SNX (bs);    /* eat any LF following */
  645.         i--;
  646.           }
  647.         case '\012':    /* newline, possible end of logical line */
  648.           if (!i || ((CHR (bs) != ' ') && (CHR (bs) != '\t')))
  649.         *s1 = c = '\0';    /* tie off unless continuation */
  650.           break;
  651.         case '\t':        /* tab */
  652.         case ' ':        /* insert whitespace if not already there */
  653.           if (c != ' ') *s1++ = c = ' ';
  654.           break;
  655.         default:        /* all other characters */
  656.           *s1++ = c = c1;    /* insert the character into the line */
  657.           break;
  658.         }
  659.                 /* end of data ties off the header */
  660.         if (!--i) *s1++ = c = '\0';
  661.       }
  662.                 /* find header item type */
  663.       if (s = strchr (t,':')) {
  664.         *s++ = '\0';    /* tie off header item, point at its data */
  665.                 /* flush whitespace */
  666.         while (*s == ' ') s++;
  667.         if (s1 = strchr (ucase (t),' ')) *s1 = '\0';
  668.         if ((t[0] == 'C') && (t[1] == 'O') && (t[2] == 'N') &&
  669.         (t[3] == 'T') && (t[4] == 'E') && (t[5] == 'N') &&
  670.         (t[6] == 'T') && (t[7] == '-'))
  671.           rfc822_parse_content_header (&part->body,t+8,s);
  672.       }
  673.     }            /* skip header trailing (CR)LF */
  674.     if (i && (CHR (bs) =='\015')) {i--; c1 = SNX (bs);}
  675.     if (i && (CHR (bs) =='\012')) {i--; c1 = SNX (bs);}
  676.     j = bs->size;        /* save upper level size */
  677.       }
  678.                 /* set offset for next level, fake size to i */
  679.       bs->size = (part->offset = GETPOS (bs)) + i;
  680.                 /* now parse it */
  681.       rfc822_parse_content (&part->body,bs,h,t);
  682.       bs->size = j;        /* restore current level size */
  683.     }
  684.     break;
  685.   default:            /* nothing special to do in any other case */
  686.     break;
  687.   }
  688. }
  689.  
  690. /* Parse RFC822 body content header
  691.  * Accepts: body to write to
  692.  *        possible content name
  693.  *        remainder of header
  694.  */
  695.  
  696. void rfc822_parse_content_header (BODY *body,char *name,char *s)
  697. {
  698.   PARAMETER *param = NIL;
  699.   char tmp[MAILTMPLEN];
  700.   char c,*t;
  701.   long i;
  702.   switch (*name) {
  703.   case 'I':            /* possible Content-ID */
  704.     if (!(strcmp (name+1,"D") || body->id)) body->id = cpystr (s);
  705.     break;
  706.   case 'D':            /* possible Content-Description */
  707.     if (!(strcmp (name+1,"ESCRIPTION") || body->description))
  708.       body->description = cpystr (s);
  709.     break;
  710.   case 'M':            /* possible Content-MD5 */
  711.     if (!(strcmp (name+1,"D5") || body->md5)) body->md5 = cpystr (s);
  712.     break;
  713.   case 'T':            /* possible Content-Type/Transfer-Encoding */
  714.     if (!(strcmp (name+1,"YPE") || body->type || body->subtype ||
  715.       body->parameter)) {
  716.                 /* get type word */
  717.       if (!(name = rfc822_parse_word (s,ptspecials))) break;
  718.       c = *name;        /* remember delimiter */
  719.       *name = '\0';        /* tie off type */
  720.       ucase (s);        /* search for body type */
  721.       for (i=0; (i<=TYPEMAX) && body_types[i] && strcmp(s,body_types[i]); i++);
  722.       if (i > TYPEMAX) body->type = TYPEOTHER;
  723.       else {            /* if empty slot, assign it to this type */
  724.     if (!body_types[i]) body_types[i] = cpystr (s);
  725.     body->type = (unsigned short) i;
  726.       }
  727.       *name = c;        /* restore delimiter */
  728.       rfc822_skipws (&name);    /* skip whitespace */
  729.       if ((*name == '/') &&    /* subtype? */
  730.       (name = rfc822_parse_word ((s = ++name),ptspecials))) {
  731.     c = *name;        /* save delimiter */
  732.     *name = '\0';        /* tie off subtype */
  733.     rfc822_skipws (&s);    /* copy subtype */
  734.     body->subtype = ucase (cpystr (s ? s :
  735.                        rfc822_default_subtype (body->type)));
  736.     *name = c;        /* restore delimiter */
  737.     rfc822_skipws (&name);    /* skip whitespace */
  738.       }
  739.                 /* subtype defaulting is a no-no, but... */
  740.       else {
  741.     body->subtype = cpystr (rfc822_default_subtype (body->type));
  742.     if (!name) {        /* did the fool have a subtype delimiter? */
  743.       name = s;        /* barf, restore pointer */
  744.       rfc822_skipws (&name);/* skip leading whitespace */
  745.     }
  746.       }
  747.  
  748.                 /* parameter list? */
  749.       while (name && (*name == ';') &&
  750.          (name = rfc822_parse_word ((s = ++name),ptspecials))) {
  751.     c = *name;        /* remember delimiter */
  752.     *name = '\0';        /* tie off attribute name */
  753.     rfc822_skipws (&s);    /* skip leading attribute whitespace */
  754.     if (!*s) *name = c;    /* must have an attribute name */
  755.     else {            /* instantiate a new parameter */
  756.       if (body->parameter) param = param->next = mail_newbody_parameter ();
  757.       else param = body->parameter = mail_newbody_parameter ();
  758.       param->attribute = ucase (cpystr (s));
  759.       *name = c;        /* restore delimiter */
  760.       rfc822_skipws (&name);/* skip whitespace before equal sign */
  761.       if ((*name != '=') ||    /* missing value is a no-no too */
  762.           !(name = rfc822_parse_word ((s = ++name),ptspecials)))
  763.         param->value = cpystr ("UNKNOWN_PARAMETER_VALUE");
  764.       else {        /* good, have equals sign */
  765.         c = *name;        /* remember delimiter */
  766.         *name = '\0';    /* tie off value */
  767.         rfc822_skipws (&s);    /* skip leading value whitespace */
  768.         if (*s) param->value = rfc822_cpy (s);
  769.         *name = c;        /* restore delimiter */
  770.         rfc822_skipws (&name);
  771.       }
  772.     }
  773.       }
  774.       if (!name) {        /* must be end of poop */
  775.     if (param && param->attribute)
  776.       sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
  777.     else strcpy (tmp,"Missing parameter");
  778.     mm_log (tmp,PARSE);
  779.       }
  780.       else if (*name) {        /* must be end of poop */
  781.     sprintf (tmp,"Unexpected characters at end of parameters: %.80s",name);
  782.     mm_log (tmp,PARSE);
  783.       }
  784.     }
  785.     else if (!strcmp (name+1,"RANSFER-ENCODING")) {
  786.                 /* flush out any confusing whitespace */
  787.       if (t = strchr (ucase (s),' ')) *t = '\0';
  788.                 /* search for body encoding */
  789.       for (i = 0; (i <= ENCMAX) && body_encodings[i] &&
  790.        strcmp (s,body_encodings[i]); i++);
  791.       if (i > ENCMAX) body->type = ENCOTHER;
  792.       else {            /* if empty slot, assign it to this type */
  793.     if (!body_encodings[i]) body_encodings[i] = cpystr (s);
  794.     body->encoding = (unsigned short) i;
  795.       }
  796.     }
  797.     break;
  798.   default:            /* otherwise unknown */
  799.     break;
  800.   }
  801. }
  802.  
  803. /* Parse RFC822 address list
  804.  * Accepts: address list to write to
  805.  *        input string
  806.  *        default host name
  807.  */
  808.  
  809. void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host)
  810. {
  811.   char c,*s,tmp[MAILTMPLEN];
  812.   ADDRESS *last = *lst;
  813.   ADDRESS *adr;
  814.   if (!string) return;        /* no string */
  815.   rfc822_skipws (&string);    /* skip leading WS */
  816.   if (!*string) return;        /* empty string */
  817.                 /* run to tail of list */
  818.   if (last) while (last->next) last = last->next;
  819.   while (string) {        /* loop until string exhausted */
  820.                 /* got an address? */
  821.     if (adr = rfc822_parse_address (lst,last,&string,host)) {
  822.       last = adr;
  823.                 /* analyze what follows */
  824.       if (string) switch (c = *string) {
  825.       case ',':            /* comma? */
  826.     ++string;        /* then another address follows */
  827.     break;
  828.       default:
  829.     s = isalnum (c) ? "Must use comma to separate addresses: %.80s" :
  830.       "Unexpected characters at end of address: %.80s";
  831.     sprintf (tmp,s,string);
  832.     mm_log (tmp,PARSE);
  833.     last = last->next = mail_newaddr ();
  834.     last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS");
  835.     last->host = cpystr (errhst);
  836.                 /* falls through */
  837.       case '\0':        /* null-specified address? */
  838.     string = NIL;        /* punt remainder of parse */
  839.     break;
  840.       }
  841.     }
  842.     else if (string) {        /* bad mailbox */
  843.       rfc822_skipws (&string);    /* skip WS */
  844.       if (!*string) strcpy (tmp,"Missing address after comma");
  845.       else sprintf (tmp,"Invalid mailbox list: %.80s",string);
  846.       mm_log (tmp,PARSE);
  847.       string = NIL;
  848.       (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS");
  849.       adr->host = cpystr (errhst);
  850.       if (last) last = last->next = adr;
  851.       else *lst = last = adr;
  852.       break;
  853.     }
  854.   }
  855. }
  856.  
  857. /* Parse RFC822 address
  858.  * Accepts: address list to write to
  859.  *        tail of address list
  860.  *        pointer to input string
  861.  *        default host name
  862.  */
  863.  
  864. ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string,
  865.                    char *defaulthost)
  866. {
  867.   ADDRESS *adr;
  868.   if (!*string) return NIL;    /* no string */
  869.   rfc822_skipws (string);    /* skip leading WS */
  870.   if (!**string) return NIL;    /* empty string */
  871.   if (adr = rfc822_parse_group (lst,last,string,defaulthost)) last = adr;
  872.                 /* got an address? */
  873.   else if (adr = rfc822_parse_mailbox (string,defaulthost)) {
  874.     if (!*lst) *lst = adr;    /* yes, first time through? */
  875.     else last->next = adr;    /* no, append to the list */
  876.     last = adr;            /* set for subsequent linking */
  877.   }
  878.   else if (*string) return NIL;
  879.   return last;
  880. }
  881.  
  882. /* Parse RFC822 group
  883.  * Accepts: address list to write to
  884.  *        pointer to tail of address list
  885.  *        pointer to input string
  886.  *        default host name
  887.  */
  888.  
  889. ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string,
  890.                  char *defaulthost)
  891. {
  892.   char tmp[MAILTMPLEN];
  893.   char *p,*s;
  894.   ADDRESS *adr;
  895.   if (!*string) return NIL;    /* no string */
  896.   rfc822_skipws (string);    /* skip leading WS */
  897.   if (!**string ||        /* trailing whitespace or not group */
  898.       ((*(p = *string) != ':') && !(p = rfc822_parse_phrase (*string))))
  899.     return NIL;
  900.   s = p;            /* end of candidate phrase */
  901.   rfc822_skipws (&s);        /* find delimiter */
  902.   if (*s != ':') return NIL;    /* not really a group */
  903.   *p = '\0';            /* tie off group name */
  904.   p = ++s;            /* continue after the delimiter */
  905.   rfc822_skipws (&p);        /* skip subsequent whitespace */
  906.                 /* write as address */
  907.   (adr = mail_newaddr ())->mailbox = rfc822_cpy (*string);
  908.   if (!*lst) *lst = adr;    /* first time through? */
  909.   else last->next = adr;    /* no, append to the list */
  910.   last = adr;            /* set for subsequent linking */
  911.   *string = p;            /* continue after this point */
  912.   while (*string && **string && (**string != ';')) {
  913.     if (adr = rfc822_parse_address (lst,last,string,defaulthost)) {
  914.       last = adr;
  915.       if (*string) {        /* anything more? */
  916.     rfc822_skipws (string);    /* skip whitespace */
  917.     switch (**string) {    /* see what follows */
  918.     case ',':        /* another address? */
  919.       ++*string;        /* yes, skip past the comma */
  920.     case ';':        /* end of group? */
  921.     case '\0':        /* end of string */
  922.       break;
  923.     default:
  924.       sprintf (tmp,"Unexpected characters after address in group: %.80s",
  925.            *string);
  926.       mm_log (tmp,PARSE);
  927.       *string = NIL;    /* cancel remainder of parse */
  928.       last = last->next = mail_newaddr ();
  929.       last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS_IN_GROUP");
  930.       last->host = cpystr (errhst);
  931.     }
  932.       }
  933.     }
  934.     else {            /* bogon */
  935.       sprintf (tmp,"Invalid group mailbox list: %.80s",*string);
  936.       mm_log (tmp,PARSE);
  937.       *string = NIL;        /* cancel remainder of parse */
  938.       (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS_IN_GROUP");
  939.       adr->host = cpystr (errhst);
  940.       last = last->next = adr;
  941.     }
  942.   }
  943.                 /* skip close delimiter */
  944.   if (*string && (**string == ';')) ++*string;
  945.                 /* append end of address mark to the list */
  946.   last->next = (adr = mail_newaddr ());
  947.   last = adr;            /* set for subsequent linking */
  948.   return last;            /* return the tail */
  949. }
  950.  
  951. /* Parse RFC822 mailbox
  952.  * Accepts: pointer to string pointer
  953.  *        default host
  954.  * Returns: address
  955.  *
  956.  * Updates string pointer
  957.  */
  958.  
  959. ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost)
  960. {
  961.   ADDRESS *adr;
  962.   char *s,*phrase;
  963.   if (!*string) return NIL;    /* no string */
  964.   rfc822_skipws (string);    /* flush leading whitespace */
  965.   if (!**string) return NIL;    /* empty string */
  966.  
  967.   /* This is much more complicated than it should be because users like
  968.    * to write local addrspecs without "@localhost".  This makes it very
  969.    * difficult to tell a phrase from an addrspec!
  970.    * The other problem we must cope with is a route-addr without a leading
  971.    * phrase.  Yuck!
  972.    */
  973.  
  974.   if (*(s = *string) == '<')     /* note start, handle case of phraseless RA */
  975.     adr = rfc822_parse_routeaddr (s,string,defaulthost);
  976.   else {            /* get phrase if any */
  977.     if ((phrase = rfc822_parse_phrase (s)) &&
  978.     (adr = rfc822_parse_routeaddr (phrase,string,defaulthost))) {
  979.       *phrase = '\0';        /* tie off phrase */
  980.                 /* phrase is a personal name */
  981.       adr->personal = rfc822_cpy (s);
  982.     }
  983.     else adr = rfc822_parse_addrspec (s,string,defaulthost);
  984.   }
  985.   return adr;            /* return the address */
  986. }
  987.  
  988. /* Parse RFC822 route-address
  989.  * Accepts: string pointer
  990.  *        pointer to string pointer to update
  991.  * Returns: address
  992.  *
  993.  * Updates string pointer
  994.  */
  995.  
  996. ADDRESS *rfc822_parse_routeaddr (char *string,char **ret,char *defaulthost)
  997. {
  998.   char tmp[MAILTMPLEN];
  999.   ADDRESS *adr;
  1000.   char *adl = NIL;
  1001.   char *routeend = NIL;
  1002.   if (!string) return NIL;
  1003.   rfc822_skipws (&string);    /* flush leading whitespace */
  1004.                 /* must start with open broket */
  1005.   if (*string != '<') return NIL;
  1006.   if (string[1] == '@') {    /* have an A-D-L? */
  1007.     adl = ++string;        /* yes, remember that fact */
  1008.     while (*string != ':') {    /* search for end of A-D-L */
  1009.                 /* punt if never found */
  1010.       if (!*string) return NIL;
  1011.       ++string;            /* try next character */
  1012.     }
  1013.     *string = '\0';        /* tie off A-D-L */
  1014.     routeend = string;        /* remember in case need to put back */
  1015.   }
  1016.                 /* parse address spec */
  1017.   if (!(adr = rfc822_parse_addrspec (++string,ret,defaulthost))) {
  1018.     if (adl) *routeend = ':';    /* put colon back since parse barfed */
  1019.     return NIL;
  1020.   }
  1021.                 /* have an A-D-L? */
  1022.   if (adl) adr->adl = cpystr (adl);
  1023.   if (*ret) if (**ret == '>') {    /* make sure terminated OK */
  1024.     ++*ret;            /* skip past the broket */
  1025.     rfc822_skipws (ret);    /* flush trailing WS */
  1026.     if (!**ret) *ret = NIL;    /* wipe pointer if at end of string */
  1027.     return adr;            /* return the address */
  1028.   }
  1029.   sprintf (tmp,"Unterminated mailbox: %.80s@%.80s",adr->mailbox,
  1030.        *adr->host == '@' ? "<null>" : adr->host);
  1031.   mm_log (tmp,PARSE);
  1032.   adr->next = mail_newaddr ();
  1033.   adr->next->mailbox = cpystr ("MISSING_MAILBOX_TERMINATOR");
  1034.   adr->next->host = cpystr (errhst);
  1035.   return adr;            /* return the address */
  1036. }
  1037.  
  1038. /* Parse RFC822 address-spec
  1039.  * Accepts: string pointer
  1040.  *        pointer to string pointer to update
  1041.  *        default host
  1042.  * Returns: address
  1043.  *
  1044.  * Updates string pointer
  1045.  */
  1046.  
  1047. ADDRESS *rfc822_parse_addrspec (char *string,char **ret,char *defaulthost)
  1048. {
  1049.   ADDRESS *adr;
  1050.   char *end;
  1051.   char c,*s,*t;
  1052.   if (!string) return NIL;    /* no string */
  1053.   rfc822_skipws (&string);    /* flush leading whitespace */
  1054.   if (!*string) return NIL;    /* empty string */
  1055.                 /* find end of mailbox */
  1056.   if (!(end = rfc822_parse_word (string,NIL))) return NIL;
  1057.   adr = mail_newaddr ();    /* create address block */
  1058.   c = *end;            /* remember delimiter */
  1059.   *end = '\0';            /* tie off mailbox */
  1060.                 /* copy mailbox */
  1061.   adr->mailbox = rfc822_cpy (string);
  1062.   *end = c;            /* restore delimiter */
  1063.   t = end;            /* remember end of mailbox for no host case */
  1064.   rfc822_skipws (&end);        /* skip whitespace */
  1065.   if (*end == '@') {        /* have host name? */
  1066.     ++end;            /* skip delimiter */
  1067.     rfc822_skipws (&end);    /* skip whitespace */
  1068.     *ret = end;            /* update return pointer */
  1069.                     /* search for end of host */
  1070.     if (end = rfc822_parse_word ((string = end),hspecials)) {
  1071.       c = *end;            /* remember delimiter */
  1072.       *end = '\0';        /* tie off host */
  1073.                 /* copy host */
  1074.       adr->host = rfc822_cpy (string);
  1075.       *end = c;            /* restore delimiter */
  1076.     }
  1077.     else {
  1078.       mm_log ("Missing or invalid host name after @",PARSE);
  1079.       adr->host = cpystr (BADHOST);
  1080.     }
  1081.   }
  1082.   else end = t;            /* make person name default start after mbx */
  1083.                 /* default host if missing */
  1084.   if (!adr->host) adr->host = cpystr (defaulthost);
  1085.   if (end && !adr->personal) {    /* try person name in comments if missing */
  1086.     while (*end == ' ') ++end;    /* see if we can find a person name here */
  1087.     if ((*end == '(') && (s = rfc822_skip_comment (&end,LONGT)) && strlen (s))
  1088.       adr->personal = rfc822_cpy (s);
  1089.     rfc822_skipws (&end);    /* skip any other WS in the normal way */
  1090.   }
  1091.                 /* set return to end pointer */
  1092.   *ret = (end && *end) ? end : NIL;
  1093.   return adr;            /* return the address we got */
  1094. }
  1095.  
  1096. /* Parse RFC822 phrase
  1097.  * Accepts: string pointer
  1098.  * Returns: pointer to end of phrase
  1099.  */
  1100.  
  1101. char *rfc822_parse_phrase (char *s)
  1102. {
  1103.   char *curpos;
  1104.   if (!s) return NIL;        /* no-op if no string */
  1105.                 /* find first word of phrase */
  1106.   curpos = rfc822_parse_word (s,NIL);
  1107.   if (!curpos) return NIL;    /* no words means no phrase */
  1108.   if (!*curpos) return curpos;    /* check if string ends with word */
  1109.   s = curpos;            /* sniff past the end of this word and WS */
  1110.   rfc822_skipws (&s);        /* skip whitespace */
  1111.                 /* recurse to see if any more */
  1112.   return (s = rfc822_parse_phrase (s)) ? s : curpos;
  1113. }
  1114.  
  1115. /* Parse RFC822 word
  1116.  * Accepts: string pointer
  1117.  * Returns: pointer to end of word
  1118.  */
  1119.  
  1120. char *rfc822_parse_word (char *s,const char *delimiters)
  1121. {
  1122.   char *st,*str;
  1123.   if (!s) return NIL;        /* no string */
  1124.   rfc822_skipws (&s);        /* flush leading whitespace */
  1125.   if (!*s) return NIL;        /* empty string */
  1126.                 /* default delimiters to standard */
  1127.   if (!delimiters) delimiters = wspecials;
  1128.   str = s;            /* hunt pointer for strpbrk */
  1129.   while (T) {            /* look for delimiter */
  1130.     if (!(st = strpbrk (str,delimiters))) {
  1131.       while (*s) ++s;        /* no delimiter, hunt for end */
  1132.       return s;            /* return it */
  1133.     }
  1134.     switch (*st) {        /* dispatch based on delimiter */
  1135.     case '"':            /* quoted string */
  1136.                 /* look for close quote */
  1137.       while (*++st != '"') switch (*st) {
  1138.       case '\0':        /* unbalanced quoted string */
  1139.     return NIL;        /* sick sick sick */
  1140.       case '\\':        /* quoted character */
  1141.     if (!*++st) return NIL;    /* skip the next character */
  1142.       default:            /* ordinary character */
  1143.     break;            /* no special action */
  1144.       }
  1145.       str = ++st;        /* continue parse */
  1146.       break;
  1147.     case '\\':            /* quoted character */
  1148.       /* This is wrong; a quoted-pair can not be part of a word.  However,
  1149.        * domain-literal is parsed as a word and quoted-pairs can be used
  1150.        * *there*.  Either way, it's pretty pathological.
  1151.        */
  1152.       if (st[1]) {        /* not on NUL though... */
  1153.     str = st + 2;        /* skip quoted character and go on */
  1154.     break;
  1155.       }
  1156.     default:            /* found a word delimiter */
  1157.       return (st == s) ? NIL : st;
  1158.     }
  1159.   }
  1160. }
  1161.  
  1162. /* Copy an RFC822 format string
  1163.  * Accepts: string
  1164.  * Returns: copy of string
  1165.  */
  1166.  
  1167. char *rfc822_cpy (char *src)
  1168. {
  1169.                 /* copy and unquote */
  1170.   return rfc822_quote (cpystr (src));
  1171. }
  1172.  
  1173.  
  1174. /* Unquote an RFC822 format string
  1175.  * Accepts: string
  1176.  * Returns: string
  1177.  */
  1178.  
  1179. char *rfc822_quote (char *src)
  1180. {
  1181.   char *ret = src;
  1182.   if (strpbrk (src,"\\\"")) {    /* any quoting in string? */
  1183.     char *dst = ret;
  1184.     while (*src) {        /* copy string */
  1185.       if (*src == '\"') src++;    /* skip double quote entirely */
  1186.       else {
  1187.     if (*src == '\\') src++;/* skip over single quote, copy next always */
  1188.     *dst++ = *src++;    /* copy character */
  1189.       }
  1190.     }
  1191.     *dst = '\0';        /* tie off string */
  1192.   }
  1193.   return ret;            /* return our string */
  1194. }
  1195.  
  1196.  
  1197. /* Copy address list
  1198.  * Accepts: address list
  1199.  * Returns: address list
  1200.  */
  1201.  
  1202. ADDRESS *rfc822_cpy_adr (ADDRESS *adr)
  1203. {
  1204.   ADDRESS *dadr;
  1205.   ADDRESS *ret = NIL;
  1206.   ADDRESS *prev = NIL;
  1207.   while (adr) {            /* loop while there's still an MAP adr */
  1208.     dadr = mail_newaddr ();    /* instantiate a new address */
  1209.     if (!ret) ret = dadr;    /* note return */
  1210.     if (prev) prev->next = dadr;/* tie on to the end of any previous */
  1211.     dadr->personal = cpystr (adr->personal);
  1212.     dadr->adl = cpystr (adr->adl);
  1213.     dadr->mailbox = cpystr (adr->mailbox);
  1214.     dadr->host = cpystr (adr->host);
  1215.     prev = dadr;        /* this is now the previous */
  1216.     adr = adr->next;        /* go to next address in list */
  1217.   }
  1218.   return (ret);            /* return the MTP address list */
  1219. }
  1220.  
  1221. /* Skips RFC822 whitespace
  1222.  * Accepts: pointer to string pointer
  1223.  */
  1224.  
  1225. void rfc822_skipws (char **s)
  1226. {
  1227.   while (T) {
  1228.     if (**s == ' ') ++*s;    /* skip space */
  1229.     else if ((**s != '(') || !rfc822_skip_comment (s,(long) NIL)) return;
  1230.   }
  1231. }
  1232.  
  1233.  
  1234. /* Skips RFC822 comment
  1235.  * Accepts: pointer to string pointer
  1236.  *        trim flag
  1237.  * Returns: pointer to first non-blank character of comment
  1238.  */
  1239.  
  1240. char *rfc822_skip_comment (char **s,long trim)
  1241. {
  1242.   char *ret,tmp[MAILTMPLEN];
  1243.   char *s1 = *s;
  1244.   char *t = NIL;
  1245.                 /* skip past whitespace */
  1246.   for (ret = ++s1; *ret == ' '; ret++);
  1247.   do switch (*s1) {        /* get character of comment */
  1248.   case '(':            /* nested comment? */
  1249.     if (!rfc822_skip_comment (&s1,(long) NIL)) return NIL;
  1250.     t = --s1;            /* last significant char at end of comment */
  1251.     break;
  1252.   case ')':            /* end of comment? */
  1253.     *s = ++s1;            /* skip past end of comment */
  1254.     if (trim) {            /* if level 0, must trim */
  1255.       if (t) t[1] = '\0';    /* tie off comment string */
  1256.       else *ret = '\0';        /* empty comment */
  1257.     }
  1258.     return ret;
  1259.   case '\\':            /* quote next character? */
  1260.     if (*++s1) break;        /* drop in if null seen */
  1261.   case '\0':            /* end of string */
  1262.     sprintf (tmp,"Unterminated comment: %.80s",*s);
  1263.     mm_log (tmp,PARSE);
  1264.     **s = '\0';            /* nuke duplicate messages in case reparse */
  1265.     return NIL;            /* this is wierd if it happens */
  1266.   case ' ':            /* whitespace isn't significant */
  1267.     break;
  1268.   default:            /* random character */
  1269.     t = s1;            /* update last significant character pointer */
  1270.     break;
  1271.   } while (s1++);
  1272.   return NIL;            /* impossible, but pacify lint et al */
  1273. }
  1274.  
  1275. /* Body contents utility and encoding/decoding routines */
  1276.  
  1277.  
  1278. /* Return body contents in normal form
  1279.  * Accepts: pointer to destination
  1280.  *        pointer to length of destination
  1281.  *        returned destination length
  1282.  *        source
  1283.  *        length of source
  1284.  *        source encoding
  1285.  * Returns: destination
  1286.  *
  1287.  * Originally, this routine was supposed to do decoding as well, but that was
  1288.  * moved to a higher level.  Now, it's merely a jacket into strcrlfcpy that
  1289.  * avoids the work for BINARY segments.
  1290.  */
  1291.  
  1292. char *rfc822_contents (char **dst,unsigned long *dstl,unsigned long *len,
  1293.                char *src,unsigned long srcl,unsigned short encoding)
  1294. {
  1295.   *len = 0;            /* in case we return an error */
  1296.   if (encoding == ENCBINARY) {    /* unmodified binary */
  1297.     if ((*len = srcl) > *dstl) {/* resize if not enough space */
  1298.       fs_give ((void **) dst);    /* fs_resize does an unnecessary copy */
  1299.       *dst = (char *) fs_get ((size_t) (*dstl = srcl) + 1);
  1300.     }
  1301.                 /* copy that many bytes */
  1302.     memcpy (*dst,src,(size_t) srcl);
  1303.     *(*dst + srcl) = '\0';    /* tie off destination */
  1304.   }
  1305.                 /* all other cases return strcrlfcpy version */
  1306.   else *len = strcrlfcpy (dst,dstl,src,srcl);
  1307.   return *dst;            /* return the string */
  1308. }
  1309.  
  1310.  
  1311. /* Output RFC 822 message
  1312.  * Accepts: temporary buffer
  1313.  *        envelope
  1314.  *        body
  1315.  *        I/O routine
  1316.  *        stream for I/O routine
  1317.  * Returns: T if successful, NIL if failure
  1318.  */
  1319.  
  1320. long rfc822_output (char *t,ENVELOPE *env,BODY *body,soutr_t f,TCPSTREAM *s)
  1321. {
  1322.   rfc822_header (t,env,body);    /* build RFC822 header */
  1323.                 /* output header and body */
  1324.   return (*f) (s,t) && (body ? rfc822_output_body (body,f,s) : T);
  1325. }
  1326.  
  1327. /* Encode a body for 7BIT transmittal
  1328.  * Accepts: envelope
  1329.  *        body
  1330.  */
  1331.  
  1332. void rfc822_encode_body_7bit (ENVELOPE *env,BODY *body)
  1333. {
  1334.   void *f;
  1335.   PART *part;
  1336.   if (body) switch (body->type) {
  1337.   case TYPEMULTIPART:        /* multi-part */
  1338.     if (!body->parameter) {    /* cookie not set up yet? */
  1339.       char tmp[MAILTMPLEN];    /* make cookie not in BASE64 or QUOTEPRINT*/
  1340.       sprintf (tmp,"%ld-%ld-%ld=:%ld",gethostid (),random (),time (0),
  1341.            getpid ());
  1342.       body->parameter = mail_newbody_parameter ();
  1343.       body->parameter->attribute = cpystr ("BOUNDARY");
  1344.       body->parameter->value = cpystr (tmp);
  1345.     }
  1346.     part = body->contents.part;    /* encode body parts */
  1347.     do rfc822_encode_body_7bit (env,&part->body);
  1348.     while (part = part->next);    /* until done */
  1349.     break;
  1350.   case TYPEMESSAGE:        /* encapsulated message */
  1351.     switch (body->encoding) {
  1352.     case ENC7BIT:
  1353.       break;
  1354.     case ENC8BIT:
  1355.       mm_log ("8-bit included message in 7-bit message body",WARN);
  1356.       break;
  1357.     case ENCBINARY:
  1358.       mm_log ("Binary included message in 7-bit message body",WARN);
  1359.       break;
  1360.     default:
  1361.       fatal ("Invalid rfc822_encode_body_7bit message encoding");
  1362.     }
  1363.     break;            /* can't change encoding */
  1364.   default:            /* all else has some encoding */
  1365.     switch (body->encoding) {
  1366.     case ENC8BIT:        /* encode 8BIT into QUOTED-PRINTABLE */
  1367.                 /* remember old 8-bit contents */
  1368.       f = (void *) body->contents.text;
  1369.       body->contents.text = rfc822_8bit (body->contents.text,body->size.bytes,
  1370.                      &body->size.bytes);
  1371.       body->encoding = ENCQUOTEDPRINTABLE;
  1372.       fs_give (&f);        /* flush old binary contents */
  1373.       break;
  1374.     case ENCBINARY:        /* encode binary into BASE64 */
  1375.       f = body->contents.binary;/* remember old binary contents */
  1376.       body->contents.text = rfc822_binary (body->contents.binary,
  1377.                        body->size.bytes,&body->size.bytes);
  1378.       body->encoding = ENCBASE64;
  1379.       fs_give (&f);        /* flush old binary contents */
  1380.     default:            /* otherwise OK */
  1381.       break;
  1382.     }
  1383.     break;
  1384.   }
  1385. }
  1386.  
  1387. /* Encode a body for 8BIT transmittal
  1388.  * Accepts: envelope
  1389.  *        body
  1390.  */
  1391.  
  1392. void rfc822_encode_body_8bit (ENVELOPE *env,BODY *body)
  1393. {
  1394.   void *f;
  1395.   PART *part;
  1396.   if (body) switch (body->type) {
  1397.   case TYPEMULTIPART:        /* multi-part */
  1398.     if (!body->parameter) {    /* cookie not set up yet? */
  1399.       char tmp[MAILTMPLEN];    /* make cookie not in BASE64 or QUOTEPRINT*/
  1400.       sprintf (tmp,"%ld-%ld-%ld=:%ld",gethostid (),random (),time (0),
  1401.            getpid ());
  1402.       body->parameter = mail_newbody_parameter ();
  1403.       body->parameter->attribute = cpystr ("BOUNDARY");
  1404.       body->parameter->value = cpystr (tmp);
  1405.     }
  1406.     part = body->contents.part;    /* encode body parts */
  1407.     do rfc822_encode_body_8bit (env,&part->body);
  1408.     while (part = part->next);    /* until done */
  1409.     break;
  1410.   case TYPEMESSAGE:        /* encapsulated message */
  1411.     switch (body->encoding) {
  1412.     case ENC7BIT:
  1413.     case ENC8BIT:
  1414.       break;
  1415.     case ENCBINARY:
  1416.       mm_log ("Binary included message in 8-bit message body",WARN);
  1417.       break;
  1418.     default:
  1419.       fatal ("Invalid rfc822_encode_body_7bit message encoding");
  1420.     }
  1421.     break;            /* can't change encoding */
  1422.   default:            /* other type, encode binary into BASE64 */
  1423.     if (body->encoding == ENCBINARY) {
  1424.       f = body->contents.binary;/* remember old binary contents */
  1425.       body->contents.text = rfc822_binary (body->contents.binary,
  1426.                        body->size.bytes,&body->size.bytes);
  1427.       body->encoding = ENCBASE64;
  1428.       fs_give (&f);        /* flush old binary contents */
  1429.     }
  1430.     break;
  1431.   }
  1432. }
  1433.  
  1434. /* Output RFC 822 body
  1435.  * Accepts: body
  1436.  *        I/O routine
  1437.  *        stream for I/O routine
  1438.  * Returns: T if successful, NIL if failure
  1439.  */
  1440.  
  1441. long rfc822_output_body (BODY *body,soutr_t f,TCPSTREAM *s)
  1442. {
  1443.   PART *part;
  1444.   PARAMETER *param;
  1445.   char *cookie = NIL;
  1446.   char tmp[MAILTMPLEN];
  1447.   char *t;
  1448.   switch (body->type) {
  1449.   case TYPEMULTIPART:        /* multipart gets special handling */
  1450.     part = body->contents.part;    /* first body part */
  1451.                 /* find cookie */
  1452.     for (param = body->parameter; param && !cookie; param = param->next)
  1453.       if (!strcmp (param->attribute,"BOUNDARY")) cookie = param->value;
  1454.     if (!cookie) cookie = "-";    /* yucky default */
  1455.     do {            /* for each part */
  1456.                 /* build cookie */
  1457.       sprintf (t = tmp,"--%s\015\012",cookie);
  1458.                 /* append mini-header */
  1459.       rfc822_write_body_header (&t,&part->body);
  1460.       strcat (t,"\015\012");    /* write terminating blank line */
  1461.                 /* output cookie, mini-header, and contents */
  1462.       if (!((*f) (s,tmp) && rfc822_output_body (&part->body,f,s))) return NIL;
  1463.     } while (part = part->next);/* until done */
  1464.                 /* output trailing cookie */
  1465.     sprintf (t = tmp,"--%s--",cookie);
  1466.     break;
  1467.   case TYPEMESSAGE:        /* encapsulated message */
  1468.     t = body->contents.msg.text;
  1469.     break;
  1470.   default:            /* all else is text now */
  1471.     t = (char *) body->contents.text;
  1472.     break;
  1473.   }
  1474.                 /* output final stuff */
  1475.   if (t && *t && !((*f) (s,t) && (*f) (s,"\015\012"))) return NIL;
  1476.   return LONGT;
  1477. }
  1478.  
  1479. /* Convert BASE64 contents to binary
  1480.  * Accepts: source
  1481.  *        length of source
  1482.  *        pointer to return destination length
  1483.  * Returns: destination as binary
  1484.  */
  1485.  
  1486. void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len)
  1487. {
  1488.   char c;
  1489.   void *ret = fs_get ((size_t) (*len = 4 + ((srcl * 3) / 4)));
  1490.   char *d = (char *) ret;
  1491.   short e = 0;
  1492.   memset (ret,0,(size_t) *len);    /* initialize block */
  1493.   *len = 0;            /* in case we return an error */
  1494.   while (srcl--) {        /* until run out of characters */
  1495.     c = *src++;            /* simple-minded decode */
  1496.     if (isupper (c)) c -= 'A';
  1497.     else if (islower (c)) c -= 'a' - 26;
  1498.     else if (isdigit (c)) c -= '0' - 52;
  1499.     else if (c == '+') c = 62;
  1500.     else if (c == '/') c = 63;
  1501.     else if (c == '=') {    /* padding */
  1502.       switch (e++) {        /* check quantum position */
  1503.       case 2:
  1504.     if (*src != '=') return NIL;
  1505.     break;
  1506.       case 3:
  1507.     e = 0;            /* restart quantum */
  1508.     break;
  1509.       default:            /* impossible quantum position */
  1510.     fs_give (&ret);
  1511.     return NIL;
  1512.       }
  1513.       continue;
  1514.     }
  1515.     else continue;        /* junk character */
  1516.     switch (e++) {        /* install based on quantum position */
  1517.     case 0:
  1518.       *d = c << 2;        /* byte 1: high 6 bits */
  1519.       break;
  1520.     case 1:
  1521.       *d++ |= c >> 4;        /* byte 1: low 2 bits */
  1522.       *d = c << 4;        /* byte 2: high 4 bits */
  1523.       break;
  1524.     case 2:
  1525.       *d++ |= c >> 2;        /* byte 2: low 4 bits */
  1526.       *d = c << 6;        /* byte 3: high 2 bits */
  1527.       break;
  1528.     case 3:
  1529.       *d++ |= c;        /* byte 3: low 6 bits */
  1530.       e = 0;            /* reinitialize mechanism */
  1531.       break;
  1532.     }
  1533.   }
  1534.   *len = d - (char *) ret;    /* calculate data length */
  1535.   return ret;            /* return the string */
  1536. }
  1537.  
  1538. /* Convert binary contents to BASE64
  1539.  * Accepts: source
  1540.  *        length of source
  1541.  *        pointer to return destination length
  1542.  * Returns: destination as BASE64
  1543.  */
  1544.  
  1545. unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len)
  1546. {
  1547.   unsigned char *ret,*d;
  1548.   unsigned char *s = (unsigned char *) src;
  1549.   char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  1550.   unsigned long i = ((srcl + 2) / 3) * 4;
  1551.   *len = i += 2 * ((i / 60) + 1);
  1552.   d = ret = (unsigned char *) fs_get ((size_t) ++i);
  1553.   for (i = 0; srcl; s += 3) {    /* process tuplets */
  1554.     *d++ = v[s[0] >> 2];    /* byte 1: high 6 bits (1) */
  1555.                 /* byte 2: low 2 bits (1), high 4 bits (2) */
  1556.     *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
  1557.                 /* byte 3: low 4 bits (2), high 2 bits (3) */
  1558.     *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
  1559.                 /* byte 4: low 6 bits (3) */
  1560.     *d++ = srcl ? v[s[2] & 0x3f] : '=';
  1561.     if (srcl) srcl--;        /* count third character if processed */
  1562.     if ((++i) == 15) {        /* output 60 characters? */
  1563.       i = 0;            /* restart line break count, insert CRLF */
  1564.       *d++ = '\015'; *d++ = '\012';
  1565.     }
  1566.   }
  1567.   *d++ = '\015'; *d++ = '\012';    /* insert final CRLF */
  1568.   *d = '\0';            /* tie off string */
  1569.   if (((unsigned long) (d - ret)) != *len) fatal ("rfc822_binary logic flaw");
  1570.   return ret;            /* return the resulting string */
  1571. }
  1572.  
  1573. /* Convert QUOTED-PRINTABLE contents to 8BIT
  1574.  * Accepts: source
  1575.  *        length of source
  1576.  *         pointer to return destination length
  1577.  * Returns: destination as 8-bit text
  1578.  */
  1579.  
  1580. unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl,
  1581.                   unsigned long *len)
  1582. {
  1583.   unsigned char *ret = (unsigned char *) fs_get ((size_t) srcl + 1);
  1584.   unsigned char *d = ret;
  1585.   unsigned char *s = d;
  1586.   unsigned char c,e;
  1587.   *len = 0;            /* in case we return an error */
  1588.   src[srcl] = '\0';        /* make sure string tied off */
  1589.   while (c = *src++) {        /* until run out of characters */
  1590.     switch (c) {        /* what type of character is it? */
  1591.     case '=':            /* quoting character */
  1592.       switch (c = *src++) {    /* what does it quote? */
  1593.       case '\0':        /* end of data */
  1594.     src--;            /* back up pointer */
  1595.     break;
  1596.       case '\015':        /* non-significant line break */
  1597.     s = d;            /* accept any leading spaces */
  1598.     if (*src == '\012') src++;
  1599.     break;
  1600.       default:            /* two hex digits then */
  1601.     if (!isxdigit (c)) {    /* must be hex! */
  1602.       fs_give ((void **) &ret);
  1603.       return NIL;
  1604.     }
  1605.     if (isdigit (c)) e = c - '0';
  1606.     else e = c - (isupper (c) ? 'A' - 10 : 'a' - 10);
  1607.     c = *src++;        /* snarf next character */
  1608.     if (!isxdigit (c)) {    /* must be hex! */
  1609.       fs_give ((void **) &ret);
  1610.       return NIL;
  1611.     }
  1612.     if (isdigit (c)) c -= '0';
  1613.     else c -= (isupper (c) ? 'A' - 10 : 'a' - 10);
  1614.     *d++ = c + (e << 4);    /* merge the two hex digits */
  1615.     s = d;            /* note point of non-space */
  1616.     break;
  1617.       }
  1618.       break;
  1619.     case ' ':            /* space, possibly bogus */
  1620.       *d++ = c;            /* stash the space but don't update s */
  1621.       break;
  1622.     case '\015':        /* end of line */
  1623.       d = s;            /* slide back to last non-space, drop in */
  1624.     default:
  1625.       *d++ = c;            /* stash the character */
  1626.       s = d;            /* note point of non-space */
  1627.     }      
  1628.   }
  1629.   *d = '\0';            /* tie off results */
  1630.   *len = d - ret;        /* calculate length */
  1631.   return ret;            /* return the string */
  1632. }
  1633.  
  1634. /* Convert 8BIT contents to QUOTED-PRINTABLE
  1635.  * Accepts: source
  1636.  *        length of source
  1637.  *         pointer to return destination length
  1638.  * Returns: destination as quoted-printable text
  1639.  */
  1640.  
  1641. #define MAXL (size_t) 75    /* 76th position only used by continuation = */
  1642.  
  1643. unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl,
  1644.                 unsigned long *len)
  1645. {
  1646.   unsigned long lp = 0;
  1647.   unsigned char *ret = (unsigned char *)
  1648.     fs_get ((((size_t) srcl) * (size_t) 3) + (((size_t) srcl) / MAXL) +
  1649.         (size_t) 2);
  1650.   unsigned char *d = ret;
  1651.   char *hex = "0123456789ABCDEF";
  1652.   unsigned char c;
  1653.   while (srcl--) {        /* for each character */
  1654.                 /* true line break? */
  1655.     if (((c = *src++) == '\015') && (*src == '\012') && srcl) {
  1656.       *d++ = '\015'; *d++ = *src++; srcl--;
  1657.       lp = 0;            /* reset line count */
  1658.     }
  1659.     else {            /* not a line break */
  1660.                 /* quoting required? */
  1661.       if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') ||
  1662.       ((c == ' ') && (*src == '\015'))) {
  1663.     if ((lp += 3) > MAXL) {    /* yes, would line overflow? */
  1664.       *d++ = '='; *d++ = '\015'; *d++ = '\012';
  1665.       lp = 3;        /* set line count */
  1666.     }
  1667.     *d++ = '=';        /* quote character */
  1668.     *d++ = hex[c >> 4];    /* high order 4 bits */
  1669.     *d++ = hex[c & 0xf];    /* low order 4 bits */
  1670.       }
  1671.       else {            /* ordinary character */
  1672.     if ((++lp) > MAXL) {    /* would line overflow? */
  1673.       *d++ = '='; *d++ = '\015'; *d++ = '\012';
  1674.       lp = 1;        /* set line count */
  1675.     }
  1676.     *d++ = c;        /* ordinary character */
  1677.       }
  1678.     }
  1679.   }
  1680.   *d = '\0';            /* tie off destination */
  1681.   *len = d - ret;        /* calculate true size */
  1682.                 /* try to give some space back */
  1683.   fs_resize ((void **) &ret,(size_t) *len + 1);
  1684.   return ret;
  1685. }
  1686.